//
//  PMDemo.m
//  PezMonkeyTest
//
//  Created by Spence DiNicolantonio on 11/3/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

#import "PMDemo.h"
#import "PMDrawingConstants.h"


#define kLineColor 0.0f, 0.0f, 0.0f
#define kCollisionColor 1.0f, 0.0f, 0.0f
#define kBodyColor 0.0f, 0.0f, 1.0f


@implementation PMDemo

@synthesize space;
@synthesize demoOptions;
@synthesize leftArrow;
@synthesize rightArrow;
@synthesize upArrow;
@synthesize downArrow;
@synthesize mousePoint;
@synthesize strings;


#pragma mark -
#pragma mark Init

/**
 * Initializes the demo with the default toggles set.
 */
- (id)init {
	return [self initWithDemoOptions:PMDemoOptionsDefault];
}

/**
 * Initializes the demo with a given set of toggles.
 */
- (id)initWithDemoOptions:(PMDemoOptions)options {
	if (![super init])
		return nil;
	
	demoOptions = options;
	leftArrow = NO;
	rightArrow = NO;
	upArrow = NO;
	downArrow = NO;
	self.strings = [NSMutableDictionary dictionary];
	return self;
}


#pragma mark -
#pragma mark Dealloc

/**
 * Deallocates the demo.
 */
- (void)dealloc {
	[self.space release];
	[super dealloc];
}


#pragma mark -
#pragma mark Drawing

/**
 * Draws all shapes and elements involved in the demo.
 */
- (void)draw {
	// get display settings
	PMDemoDrawSettings settings = [self drawSettings];
	
	// clear screen
	glClear(GL_COLOR_BUFFER_BIT);
	
	// draw spacial hash?
	if(demoOptions.drawSpacialHash) {
		cpSpaceHash *activeShapes = [space cpSpace]->activeShapes;
		[PMDemo drawSpatialHash:activeShapes];
	}
	
	// draw demo name
	NSString *name = [self demoName];
	[PMDemo drawString:name
			   atPoint:NSMakePoint(302 - 4.5*[name length], 220)];
	
	// draw strings
	for (NSValue *value in [strings allKeys]) {
		NSString *string = [strings objectForKey:value];
		NSPoint point = [value pointValue];
		[PMDemo drawString:string atPoint:point];
	}
	
	// draw bounding boxes?
	glLineWidth(1.0f);
	if(demoOptions.drawBoundingBoxes) {
		glColor3f(0.6, 1.0, 0.6);
		for (PMShape *shape in [space allShapes])
			[PMDemo drawBoundingBox:shape];
	}
	
	// draw shapes?
	glLineWidth(settings.lineWidth);
	if(settings.drawShapes) {
		for (PMShape *shape in [space allShapes])
			[PMDemo drawShape:shape withSettings:settings];
	}
	
	// draw constraints
	glColor3f(0.5f, 1.0f, 0.5f);
	for (PMConstraint *constraint in [space constraints])
		[PMDemo drawConstraint:constraint];
	
	// draw body center points?
	if(demoOptions.drawBodyPoints) {
		glPointSize(settings.bodyPointSize);
		glBegin(GL_POINTS); 
		{
			glColor3f(kLineColor);
			for (PMBody *body in [space bodies])
				glVertex2f(body.position.x, body.position.y);
		} 
		glEnd();
	}
	
	// draw collision points?
	if(demoOptions.drawCollisions) {
		glPointSize(settings.collisionPointSize);
		glBegin(GL_POINTS); 
		{
			glColor3f(kCollisionColor);
			for (PMCollision *collision in [space collisions])
				[PMDemo drawCollision:collision];
		} 
		glEnd();
	}
}

/**
 * Returns the demo options to be used for the demo. This method is called to 
 * retreive the options to be used for a demo before drawing. This method should 
 * be overridden by subclasses to set needed options.
 */
- (PMDemoDrawSettings)drawSettings {
	return PMDemoDrawSettingsDefault;
}

/**
 * Draws a given shape with a given set of options.
 */
+ (void)drawShape:(PMShape *)shape withSettings:(PMDemoDrawSettings)settings {
	// determine fill setting
	BOOL fill = (settings.fillShapes && ![shape isSensor]) || (settings.fillSensors && [shape isSensor]);
	
	
	switch([shape type]){
		case PMShapeTypeCircle:
			[self drawCircleShape:(PMCircleShape *)shape 
							 fill:fill];
			break;
		case PMShapeTypeSegment:
			[self drawSegmentShape:(PMSegmentShape *)shape
							  fill:fill];
			break;
		case PMShapeTypePolygon:
			[self drawPolygonShape:(PMPolygonShape *)shape
							  fill:fill];
			break;
		default:
			NSLog(@"Error in drawShape: Invalid PMShape Type");
	}
}

/**
 * Draws a given circle shape. If the fill flag is set to true, the circle will 
 * be filled with a random color.
 */
+ (void)drawCircleShape:(PMCircleShape *)circle fill:(BOOL)fill {
	PMBody *body = circle.body;
	
	glVertexPointer(2, GL_FLOAT, 0, circleVerts);
	glPushMatrix(); 
	{
		// apply transformation
		PMVect center = PMVectAdd(body.position, PMVectRotate(circle.center, body.rotationVector));
		glTranslatef(center.x, center.y, 0.0f);
		glRotatef(body.angle * 180.0/M_PI, 0.0f, 0.0f, 1.0f);
		glScalef(circle.radius, circle.radius, 1.0f);
		
		// fill shape?
		if (fill) {
			[PMDemo setGLColorFromPointer:circle];
			glDrawArrays(GL_TRIANGLE_FAN, 0, circleVertCount - 1);
		}
		
		// draw shape
		glColor3f(kLineColor);
		glDrawArrays(GL_LINE_STRIP, 0, circleVertCount);
	} 
	glPopMatrix();
}

/**
 * Draws a given segment shape.
 */
+ (void)drawSegmentShape:(PMSegmentShape *)segment fill:(BOOL)fill {
	PMBody *body = segment.body;
	
	PMVect start = PMVectAdd(body.position, PMVectRotate(segment.start, body.rotationVector));
	PMVect end = PMVectAdd(body.position, PMVectRotate(segment.end, body.rotationVector));
	
	if(segment.width){
		PMVect delta = PMVectSub(end, start);
		PMFloat len = PMVectLength(delta)/(segment.width/2);
		
		GLfloat verts[pillVertCount * 2];
		memcpy(verts, pillVerts, sizeof(pillVerts));
		
		for(int i=0, half=pillVertCount; i<half; i+=2)
			verts[i] += len;
		
		glVertexPointer(2, GL_FLOAT, 0, verts);
		glPushMatrix(); 
		{
			GLfloat x = start.x;
			GLfloat y = start.y;
			GLfloat cos = delta.x/len;
			GLfloat sin = delta.y/len;
			
			const GLfloat matrix[] = {
				cos,  sin,  0.0f, 0.0f,
				-sin, cos,  0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 1.0f,
				x,    y,    0.0f, 1.0f,
			};
			
			glMultMatrixf(matrix);
			
			[PMDemo setGLColorFromPointer:segment];
			if (fill)
				glDrawArrays(GL_TRIANGLE_FAN, 0, pillVertCount);
			
			glColor3f(kLineColor);
			glDrawArrays(GL_LINE_LOOP, 0, pillVertCount);
		} 
		glPopMatrix();
		
	} else {
		glColor3f(kLineColor);
		glBegin(GL_LINES); 
		{
			glVertex2f(start.x, start.y);
			glVertex2f(end.x, end.y);
		} 
		glEnd();
	}
}

/**
 * Draws a given polygon shape. If the fill flag is set to true, the polygon
 * will be filled with a random color.
 */
+ (void)drawPolygonShape:(PMPolygonShape *)polygon fill:(BOOL)fill {
	PMBody *body = polygon.body;
	
	int count = count=[polygon vertCount];
	GLfloat verts[count*2];
	glVertexPointer(2, GL_FLOAT, 0, verts);
	
	// apply transformations
	PMVect *vertArray = polygon.vertices;
	for(int i=0; i<count; i++){
		PMVect v = PMVectAdd(body.position, PMVectRotate(vertArray[i], body.rotationVector));
		verts[2*i    ] = v.x;
		verts[2*i + 1] = v.y;
	}
	
	// fill shape?
	if (fill) {
		[PMDemo setGLColorFromPointer:polygon];
		glDrawArrays(GL_TRIANGLE_FAN, 0, count);
	}
	
	// draw shape
	glColor3f(kLineColor);
	glDrawArrays(GL_LINE_LOOP, 0, count);
}

/**
 * Draws a given bounding box.
 */
+ (void)drawBoundingBox:(PMShape *)shape {
	glBegin(GL_LINE_LOOP); 
	{
		glVertex2f(shape.boundingBox.l, shape.boundingBox.b);
		glVertex2f(shape.boundingBox.l, shape.boundingBox.t);
		glVertex2f(shape.boundingBox.r, shape.boundingBox.t);
		glVertex2f(shape.boundingBox.r, shape.boundingBox.b);
	} 
	glEnd();
}

/**
 * Draws a given constraint.
 */
+ (void)drawConstraint:(PMConstraint *)constraint {
	PMBody *bodyA = constraint.bodyA;
	PMBody *bodyB = constraint.bodyB;
	
	if ([constraint isKindOfClass:PMPinJoint.class]) {
		PMPinJoint *joint = (PMPinJoint *) constraint;
		
		PMVect a = PMVectAdd(bodyA.position, PMVectRotate(joint.anchorA, bodyA.rotationVector));
		PMVect b = PMVectAdd(bodyB.position, PMVectRotate(joint.anchorB, bodyB.rotationVector));
		
		glPointSize(5.0f);
		glBegin(GL_POINTS); 
		{
			glVertex2f(a.x, a.y);
			glVertex2f(b.x, b.y);
		} 
		glEnd();
		
		glBegin(GL_LINES); 
		{
			glVertex2f(a.x, a.y);
			glVertex2f(b.x, b.y);
		} 
		glEnd();
	
	} else if ([constraint isKindOfClass:PMSlideJoint.class]) {
		PMSlideJoint *joint = (PMSlideJoint *) constraint;
		
		PMVect a = PMVectAdd(bodyA.position, PMVectRotate(joint.anchorA, bodyA.rotationVector));
		PMVect b = PMVectAdd(bodyB.position, PMVectRotate(joint.anchorB, bodyB.rotationVector));
		
		glPointSize(5.0f);
		glBegin(GL_POINTS); 
		{
			glVertex2f(a.x, a.y);
			glVertex2f(b.x, b.y);
		} 
		glEnd();
		
		glBegin(GL_LINES); 
		{
			glVertex2f(a.x, a.y);
			glVertex2f(b.x, b.y);
		} 
		glEnd();
	
	} else if ([constraint isKindOfClass:PMPivotJoint.class]) {
		PMPivotJoint *joint = (PMPivotJoint *) constraint;
		
		PMVect a = PMVectAdd(bodyA.position, PMVectRotate(joint.anchorA, bodyA.rotationVector));
		PMVect b = PMVectAdd(bodyB.position, PMVectRotate(joint.anchorB, bodyB.rotationVector));
		
		glPointSize(10.0f);
		glBegin(GL_POINTS); 
		{
			glVertex2f(a.x, a.y);
			glVertex2f(b.x, b.y);
		} 
		glEnd();
		
	} else if ([constraint isKindOfClass:PMGrooveJoint.class]) {
		PMGrooveJoint *joint = (PMGrooveJoint *)constraint;
		
		PMVect a = PMVectAdd(bodyA.position, PMVectRotate(joint.groove.start, bodyA.rotationVector));
		PMVect b = PMVectAdd(bodyA.position, PMVectRotate(joint.groove.end, bodyA.rotationVector));
		PMVect c = PMVectAdd(bodyB.position, PMVectRotate(joint.anchor, bodyB.rotationVector));
		
		glPointSize(5.0f);
		glBegin(GL_POINTS); 
		{
			glVertex2f(c.x, c.y);
		} 
		glEnd();
		
		glBegin(GL_LINES); 
		{
			glVertex2f(a.x, a.y);
			glVertex2f(b.x, b.y);
		} 
		glEnd();
	} else if([constraint isKindOfClass:PMDampedSpring.class]){
		[PMDemo drawSpring:(PMDampedSpring *)constraint];
	}
}

/**
 * Draws a given spacial hash.
 */
+ (void)drawSpatialHash:(cpSpaceHash *)hash {
	PMBoundingBox bb = PMBoundingBoxMake(-320, -240, 320, 240);
	
	PMFloat dim = hash->celldim;
	int n = hash->numcells;
	
	int l = (int) floor(bb.l / dim);
	int r = (int) floor(bb.r / dim);
	int b = (int) floor(bb.b / dim);
	int t = (int) floor(bb.t / dim);
	
	for(int i=l; i<=r; i++){
		for(int j=b; j<=t; j++){
			int cellCount = 0;
			
			int index = hashFunc(i,j,n);
			for(cpSpaceHashBin *bin = hash->table[index]; bin; bin = bin->next)
				cellCount++;
			
			GLfloat v = 1.0f - (GLfloat) cellCount / 10.0f;
			glColor3f(v, v, v);
			glRectf(i*dim, j*dim, (i + 1)*dim, (j + 1)*dim);
		}
	}
}

/**
 * Draws a given collision.
 */
+ (void)drawCollision:(PMCollision *)collision {
	for (int i=0; i<[collision contactCount]; i++) {
		PMVect point = [collision positionOfContact:i];
		glVertex2f(point.x, point.y);
	}
}

/**
 * Draws a given spring.
 */
+ (void)drawSpring:(PMDampedSpring *)spring {
	// store spring's constrained bodies for easy access
	PMBody *bodyA = spring.bodyA;
	PMBody *bodyB = spring.bodyB;
	
	// calculate spring endpoints
	PMVect a = PMVectAdd(bodyA.position, PMVectRotate(spring.anchorA, bodyA.rotationVector));
	PMVect b = PMVectAdd(bodyB.position, PMVectRotate(spring.anchorB, bodyB.rotationVector));
	
	// draw spring endpoints
	glPointSize(5.0);
	glBegin(GL_POINTS); 
	{
		glVertex2f(a.x, a.y);
		glVertex2f(b.x, b.y);
	} 
	glEnd();
	
	// calculate spring length
	PMVect delta = PMVectSub(b, a);
	
	// draw spring coils
	glVertexPointer(2, GL_FLOAT, 0, springVerts);
	glPushMatrix(); 
	{
		GLfloat x = a.x;
		GLfloat y = a.y;
		GLfloat cos = delta.x;
		GLfloat sin = delta.y;
		GLfloat s = 1.0f/PMVectLength(delta);
		
		const GLfloat matrix[] = {
			cos,  sin, 0.0f, 0.0f,
			-sin*s,  cos*s, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 1.0f,
			x,    y, 0.0f, 1.0f,
		};
		
		glMultMatrixf(matrix);
		glDrawArrays(GL_LINE_STRIP, 0, springVertCount);
	} 
	glPopMatrix();
}

/**
 * Draws a given string on the screen starting at a given point.
 */
+ (void)drawString:(NSString *)string atPoint:(NSPoint)point {
	// get C string from given NSString
	const char *cString = [string UTF8String];
	
	// determine the length of the string
	int charCount = strlen(cString);
	
	// set draw color to black
	glColor3f(0.0, 0.0, 0.0);
	
	// set raster position
	glRasterPos2f(point.x, point.y);
	
	// draw the string
	for (int i=0; i<charCount; i++) {
		if (cString[i] == '\n') {
			point.y -= 16;
			glRasterPos2f(point.x, point.y);
			
		} else {
			glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, cString[i]);
		}
	}
}

/**
 * Changes the OpenGL foreground color state to a random color.
 */
+ (void)setGLColorFromPointer:(void *)ptr {
	unsigned long val = (long)ptr;
	
	// hash the pointer up nicely
	val = (val+0x7ed55d16) + (val<<12);
	val = (val^0xc761c23c) ^ (val>>19);
	val = (val+0x165667b1) + (val<<5);
	val = (val+0xd3a2646c) ^ (val<<9);
	val = (val+0xfd7046c5) + (val<<3);
	val = (val^0xb55a4f09) ^ (val>>16);
	
	//	GLfloat v = (GLfloat)val/(GLfloat)ULONG_MAX;
	//	v = 0.95f - v*0.15f;
	//	
	//	glColor3f(v, v, v);
	
	GLubyte r = (val>>0) & 0xFF;
	GLubyte g = (val>>8) & 0xFF;
	GLubyte b = (val>>16) & 0xFF;
	
	GLubyte max = (r > g)? ((r > b)? r : b) : ((g > b)? g : b);
	
	const int mult = 127;
	const int add = 63;
	r = (r * mult) / max + add;
	g = (g * mult) / max + add;
	b = (b * mult) / max + add;
	
	glColor3ub(r, g, b);
}


#pragma mark -
#pragma mark Abstract Methods

- (void)start {}
- (void)update:(NSTimeInterval)deltaT {}
- (NSString *)demoName {
	return @"";
}

@end
